/*
 * ==========================================================================*\
 * | $Id$
 * |*-------------------------------------------------------------------------*|
 * | Copyright (C) 2009 Virginia Tech | | This file is part of CloudSpace. | |
 * CloudSpace is free software; you can redistribute it and/or modify | it under
 * the terms of the GNU General Public License as published | by the Free
 * Software Foundation; either version 3 of the License, or | (at your option)
 * any later version. | | CloudSpace is distributed in the hope that it will be
 * useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of |
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General
 * Public License for more details. | | You should have received a copy of the
 * GNU General Public License | along with CloudSpace; if not, see
 * <http://www.gnu.org/licenses/>.
 * \*==========================================================================
 */

package cloudspace.config;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import cloudspace.security.UserRegistry;


// -------------------------------------------------------------------------
/**
 * A class that wraps access to the CloudSpace configs/cloudspace.conf file to
 * provide helper methods for accessing the configuration and setters that
 * automatically persist the configuration immediately when invoked.
 * 
 * @author Tony Allevato
 * @author Last changed by $Author$
 * @version $Revision$, $Date$
 */
public class CloudSpaceConfiguration
{

    // ~ Static/instance variables .............................................

    private File cachedStorageLocation;

    private File cachedWorkDirectory;

    private static final String PERMISSIONS_TEST_FILENAME = "cloudspace.permissions.test.dat";

    private static CloudSpaceConfiguration instance;

    private static final Logger log = Logger.getLogger( CloudSpaceConfiguration.class );


    // ~ Constructors ..........................................................

    // ----------------------------------------------------------
    /**
     * Initializes the global CloudSpace configuration.
     */
    private CloudSpaceConfiguration()
    {
        /*
         * configDir = getConfigurationLocation(); if (!configDir.exists()) {
         * configDir.mkdir(); }
         * 
         * File configFile = new File(configDir, CONFIG_FILENAME); properties =
         * new ImmediateProperties(configFile);
         */
    }


    // ~ Methods ...............................................................

    // ----------------------------------------------------------
    /**
     * Gets the single instance of the global CloudSpace configuration.
     * 
     * @return the global CloudSpace configuration object
     */
    public static CloudSpaceConfiguration getInstance()
    {
        if ( instance == null )
        {
            instance = new CloudSpaceConfiguration();
        }

        return instance;
    }


    // ----------------------------------------------------------
    /**
     * Gets a value indicating whether this instance of CloudSpace is running as
     * a multi-user system.
     * 
     * A multi-user system is one which has had an administator username and
     * password set, in order to support multiple users each with his or her own
     * workspace, meant for public access on the web. Converserly, a single-user
     * system is one which does not require any user to log in, and there is a
     * single workspace location. Single-user systems should not typically be
     * made open for public access for security reasons.
     */
    public boolean isMultiUser()
    {
        return UserRegistry.getInstance().hasActiveUsers();
    }


    // ----------------------------------------------------------
    /**
     * Gets the path where configuration data and users' workspaces will be
     * stored on the local filesystem.
     * 
     * @return the user workspace location
     */
    public synchronized File getStorageLocation()
    {
        if ( cachedStorageLocation == null )
        {
            String defaultStorageLocation = ServletLocationFinder.getServletResourcePath( "storage" );

            File defaultLocation = new File( defaultStorageLocation );

            File storageLocationRef = getFirstValidConfigLocation( false );
            String storageLocation;

            if ( !defaultLocation.exists() && storageLocationRef != null )
            {
                try
                {
                    Properties props = new Properties();
                    props.load( new FileReader( storageLocationRef ) );
                    storageLocation = props.getProperty( "storageLocationPath" );
                }
                catch ( IOException e )
                {
                    storageLocation = defaultStorageLocation;
                }
            }
            else
            {
                log.info( "Using default CloudSpace storage location: "
                    + defaultStorageLocation );

                storageLocation = defaultStorageLocation;
            }

            cachedStorageLocation = new File( storageLocation );
        }

        return cachedStorageLocation;
    }


    public synchronized File getWorkLocation()
    {
        if ( cachedWorkDirectory == null )
        {
            File storageDirectory = getStorageLocation();
            File defaultWorkLocation = new File( storageDirectory, "work" );
            File storageLocationRef = getFirstValidConfigLocation( false );

            String workLocation;
            if ( storageLocationRef != null )
            {
                try
                {
                    Properties props = new Properties();
                    props.load( new FileReader( storageLocationRef ) );
                    workLocation = props.getProperty( "storageWorkPath" );
                }
                catch ( IOException e )
                {
                    workLocation = defaultWorkLocation.getAbsolutePath();
                }
            }
            else
            {
                log.info( "Using default CloudSpace storage location: "
                    + defaultWorkLocation.getAbsolutePath() );
                workLocation = defaultWorkLocation.getAbsolutePath();
            }
            if ( workLocation == null )
                cachedWorkDirectory = defaultWorkLocation;
            else
                cachedWorkDirectory = new File( workLocation );
        }
        return cachedWorkDirectory;
    }


    // ----------------------------------------------------------
    private static File getFirstValidConfigLocation( boolean isUpdating )
    {
        File file;

        // First, try the Tomcat webapps directory (the parent of the web
        // application itself).

        file = new File( ServletLocationFinder.getServletResourcePath( "/" ) ).getParentFile();
        file = new File( file, ".cloudspace_storage" );

        if ( checkConfigLocationValidity( file, isUpdating ) )
        {
            return file;
        }

        // Next, try user.home.

        file = new File( System.getProperty( "user.home" ),
            ".cloudspace_storage" );

        if ( checkConfigLocationValidity( file, isUpdating ) )
        {
            return file;
        }

        // Next, fall back to the WEB-INF directory. This will be clobbered if
        // the user upgrades by dropping in a new WAR file for auto-deployment.

        file = new File( ServletLocationFinder.getServletResourcePath( "/WEB-INF/.cloudspace_storage" ) );

        if ( checkConfigLocationValidity( file, isUpdating ) )
        {
            return file;
        }

        if ( isUpdating )
        {
            log.error( "None of the preferred locations for storing the CloudSpace "
                + "storage location path were writable; the setting will not "
                + "be preserved" );
        }

        return null;
    }


    // ----------------------------------------------------------
    private static boolean checkConfigLocationValidity(
        File location,
        boolean isUpdating )
    {
        if ( isUpdating )
        {
            log.info( "Checking write permissions at "
                + location.getAbsolutePath() );

            try
            {
                FileWriter writer = new FileWriter( location );
                writer.write( " " );
                writer.close();
            }
            catch ( IOException e )
            {
                return false;
            }

            log.info( "Write was permitted at " + location.getAbsolutePath() );

            return true;
        }
        else
        {
            log.info( "Trying to read the CloudSpace storage location from "
                + location.getAbsolutePath() );

            try
            {
                FileReader reader = new FileReader( location );
                reader.read();
                reader.close();
            }
            catch ( IOException e )
            {
                return false;
            }

            log.info( "Found CloudSpace storage location property at "
                + location.getAbsolutePath() );

            return true;
        }
    }


    // ----------------------------------------------------------
    /**
     * Sets the path where users' workspaces will hosted from in the local File
     * system. This allows for a symbolic link for
     * 
     * @param location
     *            the directory to use to host
     * @throws IOException
     *             if there was an error accessing the location
     */
    public synchronized void setWorkLocation( File location )
        throws IOException
    {
        String prefix = ServletLocationFinder.getServletResourcePath( "/" );
        if ( !location.getAbsolutePath().startsWith( prefix ) )
            throw new IOException( "The hosted files location must be within the webapp directory" );
        File originalLocation = getWorkLocation();
        if ( location.equals( originalLocation ) )
        {
            return;
        }
        checkDirectoryPermissions( location, false );

        cachedWorkDirectory = location;
        File storageLocationRef = getFirstValidConfigLocation( true );
        if ( storageLocationRef != null )
        {
            persistStorage( storageLocationRef );
        }
    }


    private void persistStorage( File storageLocationRef )
    {
        try
        {
            Properties props = new Properties();
            props.load( new FileReader( storageLocationRef ) );
            props.setProperty( "storageWorkPath",
                this.cachedWorkDirectory.getPath() );
            props.setProperty( "storageLocationPath",
                this.cachedStorageLocation.getPath() );
            props.store( new FileWriter( storageLocationRef ), null );
        }
        catch ( IOException e )
        {
            // Do nothing.
        }
    }


    // ----------------------------------------------------------
    /**
     * Sets the path where users' workspaces will be stored on the local
     * filesystem.
     * 
     * Calling this method will attempt to write to the specified location, in
     * order to verify that it is accessible by the uid under which CloudSpace
     * is running. If any problems occur when writing to the location, an
     * IOException will be thrown and the path will not be persisted into the
     * configuration file.
     * 
     * @param location
     *            the new user workspace location
     * @throws IOException
     *             if there was an error accessing the location
     */
    public synchronized void setStorageLocation( File location )
        throws IOException
    {
        File originalLocation = getStorageLocation();
        if ( location.equals( originalLocation ) )
        {
            return;
        }
        checkDirectoryPermissions( location, true );

        File storageLocationRef = getFirstValidConfigLocation( true );

        if ( storageLocationRef != null )
        {
            persistStorage( storageLocationRef );
        }
        this.cachedStorageLocation = location;
        // TODO Should we copy/move the current storage to the new location?
        // FIXME I'm just moving the conf directory for now until we have a
        // better policy for this.

        File originalConfLocation = new File( originalLocation, "conf" );
        File newConfLocation = new File( location, "conf" );
        newConfLocation.mkdirs();

        if ( originalConfLocation.exists() )
        {
            // FileUtils.moveDirectory(originalConfLocation, newConfLocation);
            FileUtils.copyDirectory( originalConfLocation, newConfLocation );
            FileUtils.deleteDirectory( originalConfLocation );
        }
    }


    // ----------------------------------------------------------
    public File getFileInStorage( String path )
    {
        return getFileInStorage( path, false );
    }


    // ----------------------------------------------------------
    public File getFileInStorage( String path, boolean createDirs )
    {
        File file = new File( getStorageLocation(), path );

        if ( createDirs )
        {
            file.getParentFile().mkdirs();
        }

        return file;
    }


    // ----------------------------------------------------------
    /**
     * Verifies that the user under which CloudSpace is running has the
     * necessary permissions to read and write to the specified path.
     */
    private static void checkDirectoryPermissions( File path, boolean create )
        throws IOException
    {
        if ( create )
        {
            if ( !path.exists() && !path.mkdirs() )
            {
                throw new IOException( "CloudSpace was unable to create the directory \""
                    + path
                    + "\". Please verify that the account under which "
                    + "Apache Tomcat is running has write permissions for "
                    + "this location." );
            }
        }

        File testFile = new File( path, PERMISSIONS_TEST_FILENAME );

        try
        {
            // Ensure that we have write permissions.

            FileWriter writer = new FileWriter( testFile );
            writer.write( "testing write permissions" );
            writer.close();
        }
        catch ( IOException e )
        {
            throw new IOException( "CloudSpace was unable to write at the location \""
                + path
                + "\". Please verify that the account under which "
                + "Apache Tomcat is running has write permissions for "
                + "this location.",
                e );
        }

        // Ensure that we have read permissions.

        try
        {
            FileReader reader = new FileReader( testFile );
            reader.read();
            reader.close();
        }
        catch ( IOException e )
        {
            throw new IOException( "CloudSpace was unable to read from the location \""
                + path
                + "\". Please verify that the account under which "
                + "Apache Tomcat is running has read permissions for this "
                + "this location.",
                e );
        }

        boolean wasDeleted = testFile.delete();

        if ( !wasDeleted )
        {
            throw new IOException( "Could not delete the permissions test file" );
        }
    }


    public void initializeStore() throws IOException
    {
        File store = getStorageLocation();
        store.mkdir();
        File conf = new File( store, "conf" );
        conf.mkdir();
        File userConf = new File( conf, "user.conf" );
        File groupConf = new File( conf, "permissions.conf" );
        userConf.createNewFile();
        groupConf.createNewFile();
        File data = new File( store, "data" );
        data.mkdir();
        File template = new File( store, "template" );
        template.mkdir();
        File work = new File( store, "work" );
        work.mkdir();
        File snapshot = new File( store, "snapshot" );
        snapshot.mkdir();

    }
}
